﻿using System;
using System.Collections.Concurrent;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.IO;
using ServerFramework.Tools;
using System.Reflection;
using System.Collections.Generic;
using Development.Tools;


namespace ServerFramework.Script
{

    /// <summary>
    /// 脚本类信息,本类缓存一些脚本类的信息，用来加速反射调用
    /// </summary>
    public class ScriptClassInfo
    {
        /// <summary>
        /// 脚本类对象
        /// </summary>
        private object _scriptObj;

        /// <summary>
        /// 所有的成员变量
        /// </summary>
        private Dictionary<string, FieldInfo> _fields = new Dictionary<string, FieldInfo>();
        /// <summary>
        /// 所有的属性
        /// </summary>
        private Dictionary<string, PropertyInfo> _propertys = new Dictionary<string, PropertyInfo>();
        /// <summary>
        /// 所有的成员方法
        /// </summary>
        private Dictionary<string, MethodInfo> _memberMethods = new Dictionary<string, MethodInfo>();

        /// <summary>
        /// 所有的静态方法
        /// </summary>
        private Dictionary<string, MethodInfo> _staticMethods = new Dictionary<string, MethodInfo>();


        public ScriptClassInfo(object scriptObj)
        {
            _scriptObj = scriptObj;
            var fields = _scriptObj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
            foreach (var f in fields)
            {
                _fields.Add(f.Name, f);
            }
            var propertys = _scriptObj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (var p in propertys)
            {
                _propertys.Add(p.Name, p);
            }

            var memberMethods = _scriptObj.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public);
            foreach (var m in memberMethods)
            {
                _memberMethods.Add(m.Name, m);
            }

            var staticMethods = _scriptObj.GetType().GetMethods(BindingFlags.Public | BindingFlags.Static);
            foreach (var s in staticMethods)
            {
                _staticMethods.Add(s.Name, s);
            }
        }


        /// <summary>
        /// 条用成员方法
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public object InvokeMemberMethod(string methodName,params object[] args)
        {
            MethodInfo method = null;
            if(_memberMethods.TryGetValue(methodName,out method))
            {
                return method.Invoke(_scriptObj, args);
            }
            return null;
        }

        /// <summary>
        /// 条用静态方法
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public object InvokeStaticMethod(string methodName,params object[] args)
        {
            MethodInfo method = null;
            if (_staticMethods.TryGetValue(methodName, out method))
            {
                return method.Invoke(_scriptObj, args);
            }
            return null;
        }

        /// <summary>
        /// 获取的成员
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object GetField(string name)
        {
            FieldInfo info = null;
            if (_fields.TryGetValue(name, out info))
            {
                return info.GetValue(_scriptObj);
            }
            return null;
        }

        /// <summary>
        /// 设置成员
        /// </summary>
        /// <param name="name"></param>
        /// <param name="val"></param>
        public void SetField(string name, object val)
        {
            FieldInfo info = null;
            if (_fields.TryGetValue(name, out info))
            {
                info.SetValue(_scriptObj, val);
            }
        }


        /// <summary>
        /// 获取属性
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object GetProperty(string name)
        {
            PropertyInfo info = null;
            if (_propertys.TryGetValue(name, out info))
            {
                return info.GetValue(_scriptObj);
            }
            return null;
        }

        public void SetProperty(string name, object val)
        {
            PropertyInfo info = null;
            if (_propertys.TryGetValue(name, out info))
            {
                info.SetValue(_scriptObj, val);
            }
        }
    }

    /// <summary>
    /// cs 脚本
    /// </summary>
    public class CsScript : IDisposable
    {
        private static CsScript _instance = null;
        public static CsScript Instance { get { if (_instance == null) { _instance = new CsScript(); } return _instance; } }
        private readonly string _codeName = "Cs";
        private Dictionary<string, ScriptClassInfo> _classes = new Dictionary<string, ScriptClassInfo>();
        private FileSystemWatcher _fileWatcher = null;

        //加载脚本
        public void LoadScript(string [] referencedAssemblies = null)
        {
            CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();
            CompilerParameters objCompilerParameters = new CompilerParameters();
            objCompilerParameters.ReferencedAssemblies.Add("ServerFramework.dll");

            ///加入当前程序集
            Assembly asm = Assembly.GetEntryAssembly();
            if (asm != null)
            {
                objCompilerParameters.ReferencedAssemblies.Add(asm.GetName().Name + ".exe");
            }

            if(referencedAssemblies != null)
            {
                objCompilerParameters.ReferencedAssemblies.AddRange(referencedAssemblies);
            }
            objCompilerParameters.GenerateExecutable = false;
            objCompilerParameters.GenerateInMemory = true;
            var files = ScriptUtil.GetScriptFiles(_codeName);
            CompilerResults result = objCSharpCodePrivoder.CompileAssemblyFromFile(objCompilerParameters, files.ToArray());
            if(result.Errors.Count != 0)
            {
                foreach(var er in result.Errors)
                {
                    Log.WriteLog(ELogLevel.LOG_ERROR, er.ToString());
                }
                return;
            }
            Assembly assembly = result.CompiledAssembly;
            var types =  assembly.GetTypes();
            foreach(var t in types)
            {
                _classes.Add(t.Name, new ScriptClassInfo(GlobalDefine.CreateObject(t)));
            }
            AddFileWatcher(ScriptUtil.GetScriptPath(_codeName), "*.cs");
        }

        private void AddFileWatcher(string path, string filter)
        {
            if (_fileWatcher == null)
            {
                _fileWatcher = new FileSystemWatcher(path, filter);
                _fileWatcher.Changed += watcher_Changed;
                _fileWatcher.Created += watcher_Changed;
                _fileWatcher.EnableRaisingEvents = true;
            }

        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            try
            {
                CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();
                CompilerParameters objCompilerParameters = new CompilerParameters();
                objCompilerParameters.GenerateExecutable = false;
                objCompilerParameters.GenerateInMemory = true;
                CompilerResults result = objCSharpCodePrivoder.CompileAssemblyFromFile(objCompilerParameters, e.FullPath);
                if (result.Errors.Count != 0)
                {
                    foreach (var er in result.Errors)
                    {
                        Log.WriteLog(ELogLevel.LOG_ERROR, er.ToString());
                    }
                    return;
                }
                Assembly assembly = result.CompiledAssembly;
                var types = assembly.GetTypes();
                foreach (var t in types)
                {
                    if (_classes.ContainsKey(t.Name))
                    {
                        _classes[t.Name] =  new ScriptClassInfo(GlobalDefine.CreateObject(t));
                    }
                    else
                    {
                        _classes.Add(t.Name, new ScriptClassInfo(GlobalDefine.CreateObject(t)));
                    }
                    Log.WriteLog(ELogLevel.LOG_INFO, "reload cs name=" + t.Name);
                }
            }
            catch (Exception ex)
            {
                Log.WriteLog(ELogLevel.LOG_ERROR, ex.Message);
            }
        }

        public static ScriptClassInfo GetScriptObj(string className)
        {
            return CsScript.Instance._GetScriptObj(className);
        }

        private ScriptClassInfo _GetScriptObj(string className)
        {
            if (_classes.ContainsKey(className))
            {
                return _classes[className];
            }
            return null;
        }

        public void Dispose()
        {
            if(_fileWatcher != null)
            {
                _fileWatcher.Dispose();
            }
        }
         
    }
}
